import { DataSource } from './../domain/models/dataSource';
import { WorkflowNodeItem } from './../domain/models/workflowNodeItem';
import { TmpComItem } from './../domain/models/tmpComItem';
import { Template } from './../domain/models/template';
import { PageTmpItem } from './../domain/models/pageTmpItem';
import { OrgPageItem } from './../domain/models/orgPageItem';
import { FlowNode } from './../domain/models/flowNode';
import { Workflow } from './../domain/models/workflow';
import { Menu } from './../domain/models/menu';
import { Page } from './../domain/models/page';
import { OrgCodeRule } from './../domain/models/orgCodeRule';
import { Guid } from './../common/utils/guid';
import { Transaction } from "tiny-entity2";
import { IQueryObject } from "tiny-entity2/queryObject";
import { ApiDataContext } from "../domain/apiDataContext";
import { Organization } from "../domain/models/organization";
import { BaseService, QueryParamsBase } from "./baseService";
import { ServiceException } from "../common/excpetion";
import { OrgCodeSeed } from '../domain/models/orgCodeSeed';
import * as moment from 'moment';
import { Path } from '../domain/models/path';
import { Condition } from '../domain/models/condition';

export class OrganizationService extends BaseService {
    async CreateOrganization(entity: Organization, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async (ctx) => {
            let has = await ctx.Organization.Any(x => x.name == $args1, { $args1: entity.name });
            if (has) {
                throw new ServiceException(`已经存在名称为【${entity.name}】的组织！`);
            }
            has = await ctx.Organization.Any(x => x.code == $args1, { $args1: entity.code });
            if (has) {
                throw new ServiceException(`已经存在代码为【${entity.code}】的组织！`);
            }

            let d = new Organization();
            d.ConverToEntity(entity);
            d.id = Guid.GetGuid();
            d.createTime = Date.now();
            // d.status = null;
            d.isDelete = false;
            return ctx.Create(d);
        });
    }
    async UpdateOrganization(entity: Organization, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async (ctx) => {
            let has = await ctx.Organization.Any(x => x.name == $args1 && x.id != $args2 && x.isDelete == $args3, { $args1: entity.name, $args2: entity.id, $args3: false });
            if (has) {
                throw new ServiceException(`已经存在名称为【${entity.name}】的组织！`);
            }
            has = await ctx.Organization.Any(x => x.code == $args1 && x.id != $args2 && x.isDelete == $args3, { $args1: entity.code, $args2: entity.id, $args3: false });
            if (has) {
                throw new ServiceException(`已经存在代码为【${entity.code}】的组织！`);
            }

            let d = new Organization();
            d.ConverToEntity(entity);
            return ctx.Update(d);
        });
    }

    async DeleteOrganization(entity: Organization, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async (ctx) => {
            let d = new Organization();
            d.ConverToEntity(entity);
            return ctx.Delete(d);
        });
    }

    async GetOrganizationById(id: string) {
        let ctx = new ApiDataContext();
        let r = await ctx.Organization.First(x => x.id == $args1, { $args1: id });
        if (r) {
            r.orgTree = await ctx.OrganizationTree.First(x => x.objectId == $args1, { $args1: r.id });
        }
        return r;
    }
    async QueryOrganization(params: OrganizationQueryParams) {
        let q = this.GetQueryOrganizationCtx(params);
        let total = await q.Count();

        let q2 = this.GetQueryOrganizationCtx(params);
        if (params.countOfPage && params.pageIndex) {
            q2 = q2.Take(params.countOfPage).Skip(params.pageIndex - 1) as any;
        }
        let list = await q2.OrderByDesc(x => x.createTime).ToList();

        return {
            total,
            list
        };
    }

    private GetQueryOrganizationCtx(params: OrganizationQueryParams) {
        let ctx = new ApiDataContext();
        let q = ctx.Organization as IQueryObject<Organization>;
        q = q.Where(x => x.isDelete == $args1, { $args1: false });
        if (params) {
            if (params.code) {
                q = q.Where(x => x.code == $args1, { $args1: params.code });
            }
            if (params.name) {
                q = q.Where(x => x.name.indexOf($args1), { $args1: params.name });
            }
            if (params.status) {
                q = q.Where(x => x.status == $args1, { $args1: params.status });
            }
            if (params.ids && params.ids.length > 0) {
                // q = q.Where(x => $args1.indexOf(x.id) > -1, { $args1: params.ids });
                q = q.Contains(x => x.id, params.ids) as IQueryObject<Organization>;
            }
        }
        return q;
    }

    async CreateOrgCodeRule(data: OrgCodeRule, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async ctx => {
            let entity = new OrgCodeRule();
            entity.ConverToEntity(data);
            return ctx.Create(entity);
        })
    }

    async CreateOrgCodeSeed(data: OrgCodeSeed, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async ctx => {
            let entity = new OrgCodeSeed();
            entity.ConverToEntity(data);
            entity.createTime = Date.now();
            return ctx.Create(entity);
        })
    }

    async UpdateOrgCodeSeed(data: OrgCodeSeed, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async ctx => {
            let entity = new OrgCodeSeed();
            entity.ConverToEntity(data);
            return ctx.Update(entity);
        })
    }

    async DeleteOrgCodeRuleByOrgId(orgId: string, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async ctx => {
            return ctx.Delete<OrgCodeRule>(x => x.orgId == $args1, ctx.OrgCodeRule, { $args1: orgId });
        })
    }

    GetOrgCodeSeedById(id: string) {
        let ctx = new ApiDataContext();
        return ctx.OrgCodeSeed.First(x => x.id == $args1, { $args1: id });
    }

    async GetOrgCodeRuleAndSeedsByOrgId(orgId: string) {
        let ctx = new ApiDataContext();
        let orgCodeRules = await ctx.OrgCodeRule.Where(x => x.orgId == $args1, { $args1: orgId }).ToList();
        let orgCodeSeeds = await ctx.OrgCodeSeed.Where(x => x.orgId == $args1, { $args1: orgId }).ToList();

        return {
            orgCodeRules,
            orgCodeSeeds
        }
    }

    async GenerateNewCode(orgCodeRuleName: string, orgId: string) {
        let ctx = new ApiDataContext();
        let orgCodeSetting = await ctx.DataRegister.First(x => x.dataType == "orgCodeSetting" && x.objectId == $args1, { $args1: orgId });
        if (!orgCodeSetting) {
            throw new ServiceException('当前单位未配置编码！');
        }

        let data: any[] = orgCodeSetting.data;
        let values = [];
        let orgCodeSeed;
        for (let item of data) {
            let groupItem = item.orgCodeItems.find(x => x.type == "group");
            if (groupItem) {
                let gItem = groupItem.codeGroupItems.find(x => x.codeName == orgCodeRuleName);
                if (!gItem) {
                    continue;
                }
                for (let n of item.orgCodeItems) {
                    if (n.type == "settled") {
                        orgCodeSeed = await this.GetSettledCode(values, n);
                    }
                    else if (n.type == "group") {
                        for (let l of gItem.list) {
                            if (l.type == 'date') {
                                this.GetDateCode(values, l);
                            }
                            else {
                                values.push(l.value);
                            }
                        }
                    }
                    else {
                        values.push(n.value);
                    }
                }
            }
            else {
                if (item.codeName == orgCodeRuleName) {
                    for (let n of item.orgCodeItems) {
                        if (n.type == 'settled') {
                            orgCodeSeed = await this.GetSettledCode(values, n);
                        }
                        else if (n.type == 'date') {
                            this.GetDateCode(values, n)
                        }
                        else {
                            values.push(n.value);
                        }
                    }
                }
            }
        }

        return {
            code: values.join(''),
            seedId: orgCodeSeed ? orgCodeSeed.id : ""
        };
    }

    async GetSettledCode(values: any[], item) {
        let ctx = new ApiDataContext();
        let orgCodeSeed = await ctx.OrgCodeSeed.First(x => x.id == $args1, { $args1: item.id });
        if (!orgCodeSeed) {
            throw new ServiceException('固定编码错误！');
        }

        orgCodeSeed.value += orgCodeSeed.incrementSeed;

        if (orgCodeSeed.canReset) {
            let now = moment();
            if (orgCodeSeed.resetRule == "month") {
                let diff = now.diff(moment(orgCodeSeed.createTime), "month", true);
                if (diff > 1) {
                    orgCodeSeed.value = 1;
                    orgCodeSeed.createTime = Date.now();
                }
            }
            else if (orgCodeSeed.resetRule == "quarter") {
                let diff = now.diff(moment(orgCodeSeed.createTime), "month", true);
                if (diff > 3) {
                    orgCodeSeed.value = 1;
                    orgCodeSeed.createTime = Date.now();
                }
            }
            else if (orgCodeSeed.resetRule == "year") {
                let diff = now.diff(moment(orgCodeSeed.createTime), "year", true);
                if (diff > 1) {
                    orgCodeSeed.value = 1;
                    orgCodeSeed.createTime = Date.now();
                }
            }
        }


        let v = new Array(Math.abs(orgCodeSeed.value.toString().length - orgCodeSeed.codeBit)).fill('0').join('') + orgCodeSeed.value.toString();
        values.push(v);

        return orgCodeSeed;
    }

    private GetDateCode(values: any[], item) {
        let t = [];
        if (item.showYear) {
            t.push(new Date().getFullYear());
        }
        if (item.showMonth) {
            t.push(new Date().getMonth() + 1);
        }
        if (item.showDay) {
            t.push(new Date().getDate())
        }

        values.push(t.join(item.separatorChar));
    }

    async CopyOrgConfig(param: {
        sourceOrgId: string;
        targetOrgId: string;
    }, ctx?: ApiDataContext) {
        ctx || (ctx = new ApiDataContext());
        return Transaction(ctx, async ctx => {
            const orgId = param.targetOrgId;
            await ctx.Delete<Menu>(x => x.orgId == $args1, ctx.Menu, { $args1: orgId });
            await ctx.Delete<Page>(x => x.orgId == $args1, ctx.Page, { $args1: orgId });
            await ctx.Delete<Workflow>(x => x.orgId == $args1, ctx.Workflow, { $args1: orgId });
            await ctx.Delete<WorkflowNodeItem>(x => x.orgId == $args1, ctx.WorkflowNodeItem, { $args1: orgId });
            await ctx.Delete<FlowNode>(x => x.orgId == $args1, ctx.FlowNode, { $args1: orgId });
            await ctx.Delete<OrgPageItem>(x => x.orgId == $args1, ctx.OrgPageItem, { $args1: orgId });
            await ctx.Delete<PageTmpItem>(x => x.orgId == $args1, ctx.PageTmpItem, { $args1: orgId });
            await ctx.Delete<Path>(x => x.orgId == $args1, ctx.Path, { $args1: orgId });
            await ctx.Delete<Template>(x => x.orgId == $args1, ctx.Template, { $args1: orgId });
            await ctx.Delete<TmpComItem>(x => x.orgId == $args1, ctx.TmpComItem, { $args1: orgId });
            await ctx.Delete<DataSource>(x => x.orgId == $args1, ctx.DataSource, { $args1: orgId });

            let idMaps = new Map<string, string>();

            const workflowList = await ctx.Workflow.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of workflowList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const flowNodeList = await ctx.FlowNode.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of flowNodeList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.workflowId + orgId)) {
                    data.workflowId = idMaps.get(data.workflowId + orgId);
                }

                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const pathList = await ctx.Path.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of pathList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.workflowId + orgId)) {
                    data.workflowId = idMaps.get(data.workflowId + orgId);
                }
                if (idMaps.has(data.headNodeId + orgId)) {
                    data.headNodeId = idMaps.get(data.headNodeId + orgId);
                }
                if (idMaps.has(data.endNodeId + orgId)) {
                    data.endNodeId = idMaps.get(data.endNodeId + orgId);
                }
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const workflowNodeItemList = await ctx.WorkflowNodeItem.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of workflowNodeItemList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.workflowId + orgId)) {
                    data.workflowId = idMaps.get(data.workflowId + orgId);
                }
                if (idMaps.has(data.flowNodeId + orgId)) {
                    data.flowNodeId = idMaps.get(data.flowNodeId + orgId);
                }
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const pageList = await ctx.Page.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of pageList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.flowNodeId + orgId)) {
                    data.flowNodeId = idMaps.get(data.flowNodeId + orgId);
                }
                idMaps.set(oldId + orgId, data.id);
            }
            for (let data of pageList) {
                if (data.refPageId && idMaps.has(data.refPageId + orgId)) {
                    data.refPageId = idMaps.get(data.refPageId + orgId);
                }
                await ctx.Create(data);
            }


            const templateList = await ctx.Template.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of templateList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const pageTmpItemList = await ctx.PageTmpItem.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of pageTmpItemList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.pageId + orgId)) {
                    data.pageId = idMaps.get(data.pageId + orgId);
                }
                if (idMaps.has(data.templateId + orgId)) {
                    data.templateId = idMaps.get(data.templateId + orgId);
                }
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const menuList = await ctx.Menu.Where(x => x.orgId == $args1 && x.isDelete == false, { $args1: param.sourceOrgId }).ToList();
            for (let data of menuList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (data.pageId && idMaps.has(data.pageId + orgId)) {
                    data.pageId = idMaps.get(data.pageId + orgId);
                }
                if (data.workflowId && idMaps.has(data.workflowId + orgId)) {
                    data.workflowId = idMaps.get(data.workflowId + orgId);
                }

                idMaps.set(oldId + orgId, data.id);
            }
            for (let data of menuList) {
                if (data.parentId && idMaps.has(data.parentId + orgId)) {
                    data.parentId = idMaps.get(data.parentId + orgId);
                }
                await ctx.Create(data);
            }

            //copy datasource
            const dsList = await ctx.DataSource.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of dsList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (data.objectId && idMaps.has(data.objectId + orgId)) {
                    data.objectId = idMaps.get(data.objectId + orgId);
                }
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            //copy orgPageItem
            const orgPageItemList = await ctx.OrgPageItem.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of orgPageItemList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.pageId + orgId)) {
                    data.pageId = idMaps.get(data.pageId + orgId);
                }
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }

            const tmpComItemList = await ctx.TmpComItem.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of tmpComItemList) {
                const oldId = data.id;
                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (idMaps.has(data.templateId + orgId)) {
                    data.templateId = idMaps.get(data.templateId + orgId);
                }
                idMaps.set(oldId + orgId, data.id);
            }
            for (let data of tmpComItemList) {
                if (data.component.selector == 'base-table') {
                    if (idMaps.has(data.component.dataSource as any + orgId)) {
                        data.component.dataSource = idMaps.get(data.component.dataSource as any + orgId) as any;
                    }
                    if (data.component.extends.pageId && idMaps.has(data.component.extends.pageId + orgId)) {
                        data.component.extends.pageId = idMaps.get(data.component.extends.pageId + orgId);
                    }
                }
                if (data.component.selector == 'select') {
                    let ds: any = data.component.dataSource;
                    if (ds && ds.type == 'innerDs' && idMaps.has(ds.dataSourceId + orgId)) {
                        ds.dataSourceId = idMaps.get(ds.dataSourceId + orgId);
                    }
                    data.component.dataSource = ds;
                    if (data.component.extends.refTmpComItemIds) {
                        let ids = [];
                        data.component.extends.refTmpComItemIds.forEach(id => {
                            if (idMaps.has(id + orgId)) {
                                ids.push(idMaps.get(id + orgId));
                            }
                        });
                        data.component.extends.refTmpComItemIds = ids;
                    }
                }
                if (data.component.selector == 'tabs') {
                    if (data.component.extends.tabItemConfigs && data.component.extends.tabItemConfigs.length > 0) {
                        data.component.extends.tabItemConfigs.forEach(item => {
                            let tmpIds = [];
                            if(!item.templateIds) return;
                            item.templateIds.forEach(tmpId => {
                                if (idMaps.has(tmpId + orgId)) {
                                    tmpIds.push(idMaps.get(tmpId + orgId));
                                }
                            });
                            item.templateIds = tmpIds;
                        });
                    }
                }
                if (data.component.selector == 'auto-form') {
                    if (data.component.extends.fields) {
                        data.component.extends.fields.forEach(item => {
                            if (item.tmpComItemId && idMaps.has(item.tmpComItemId + orgId)) {
                                item.tmpComItemId = idMaps.get(item.tmpComItemId + orgId)
                            }
                        });
                    }

                    if (data.component.extends.refTmpComItem && idMaps.has(data.component.extends.refTmpComItem.tplComponentTempId + orgId)) {
                        data.component.extends.refTmpComItem.tplComponentTempId = idMaps.get(data.component.extends.refTmpComItem.tplComponentTempId + orgId);
                    }
                }
                if (data.component.selector == 'column-table') {
                    if (data.component.extends.fields) {
                        data.component.extends.fields.forEach(item => {
                            if (item.tmpComItemId && idMaps.has(item.tmpComItemId + orgId)) {
                                item.tmpComItemId = idMaps.get(item.tmpComItemId + orgId)
                            }
                        });
                    }
                    if (data.component.extends.loop && idMaps.has(data.component.extends.loop.tplComponentTempId + orgId)) {
                        data.component.extends.loop.tplComponentTempId = idMaps.get(data.component.extends.loop.tplComponentTempId + orgId);
                    }
                }
                if (data.component.selector == 'rmb-capitalize') {
                    if (data.component.extends.refTplComponent && idMaps.has(data.component.extends.refTplComponent.tplComponentTempId + orgId)) {
                        data.component.extends.refTplComponent.tplComponentTempId = idMaps.get(data.component.extends.refTplComponent.tplComponentTempId + orgId);
                    }
                }
                if (data.component.selector == 'doc-assembly') {
                    if (data.component.extends.fields) {
                        data.component.extends.fields.forEach(item => {
                            if (idMaps.has(item.tmpComItemId + orgId)) {
                                item.tmpComItemId = idMaps.get(item.tmpComItemId + orgId);
                            }
                        });
                    }
                }
                if (data.component.selector == 'task-member') {
                    data.component.extends.selectedUsers = null;
                }
                if (data.component.selector == 'early-warning') {
                    if (data.component.extends.spaceChars) {
                        data.component.extends.spaceChars.forEach(item => {
                            if (idMaps.has(item.tmpComItemId + orgId)) {
                                item.tmpComItemId = idMaps.get(item.tmpComItemId + orgId);
                            }
                        });
                    }
                }
                if (data.component.selector == 'search-bar') {
                    if (data.component.extends.bindComTmpId && idMaps.has(data.component.extends.bindComTmpId + orgId)) {
                        data.component.extends.bindComTmpId = idMaps.get(data.component.extends.bindComTmpId + orgId);
                    }
                }
                if (data.component.selector == 'button-group') {
                    if (data.component.extends.functionButton) {
                        data.component.extends.functionButton.forEach(item => {
                            if (idMaps.has(item.linkPage + orgId)) {
                                item.linkPage = idMaps.get(item.linkPage + orgId);
                            }
                        });
                    }
                }

                await ctx.Create(data);
            }


            const conditionList = await ctx.Condition.Where(x => x.orgId == $args1, { $args1: param.sourceOrgId }).ToList();
            for (let data of conditionList) {
                const oldId = data.id;

                data.orgId = orgId;
                data.id = Guid.GetGuid();
                if (data.objectId && idMaps.has(data.objectId + orgId)) {
                    data.objectId = idMaps.get(data.objectId + orgId);
                }
                await ctx.Create(data);
                idMaps.set(oldId + orgId, data.id);
            }
        })
    }
}

export interface OrganizationQueryParams extends QueryParamsBase {
    code?: string;
    name?: string;
    status?: string;
    ids?: string[];
}